package com.yifeng.repo.controller.excel.loader.task.listener;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.gomcarter.frameworks.base.exception.CustomException;
import com.yifeng.repo.controller.excel.loader.dto.RowDataDto;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Consumer;

/**
 * Created by daibing on 2023/1/23.
 */
@Slf4j
public class MapDataAsyncReadListener extends AnalysisEventListener<Map<Integer, String>> {
    private final Map<Integer, String> headMap;
    private final int batchSize;
    private final boolean trace;
    private final List<RowDataDto> rowDataList;
    private final Consumer<List<RowDataDto>> rowDataConsumer;
    private final Consumer<Integer> totalRowNumberConsumer;

    /**
     * 状态数据等
     */
    private int readCount = 0;

    /**
     * 仅针对简单excel：表头就一行，并且直接表头直接是英文编号，建议谨慎使用。
     */
    public MapDataAsyncReadListener(Consumer<List<RowDataDto>> rowDataConsumer, Consumer<Integer> totalRowNumberConsumer) {
        this(Collections.emptyMap(), 500, false, rowDataConsumer, totalRowNumberConsumer);
    }

    public MapDataAsyncReadListener(Map<Integer, String> headMap, int batchSize,
                                    Consumer<List<RowDataDto>> rowDataConsumer, Consumer<Integer> totalRowNumberConsumer) {
        this(headMap, batchSize, false, rowDataConsumer, totalRowNumberConsumer);
    }

    public MapDataAsyncReadListener(Map<Integer, String> headMap, int batchSize, boolean trace,
                                    Consumer<List<RowDataDto>> rowDataConsumer, Consumer<Integer> totalRowNumberConsumer) {
        this.headMap = headMap;
        this.batchSize = batchSize;
        this.trace = trace;
        this.rowDataList = new ArrayList<>(batchSize);
        this.rowDataConsumer = rowDataConsumer;
        this.totalRowNumberConsumer = totalRowNumberConsumer;
    }

    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        if (this.headMap.isEmpty()) {
            this.headMap.putAll(headMap);
        }
    }

    @Override
    public void invoke(Map<Integer, String> data, AnalysisContext context) {
        // 1. 读取excel的大致数据总行数
        totalRowNumberConsumer.accept(context.readSheetHolder().getApproximateTotalRowNumber());

        // 2. 读取数据，格式转换，并且写入列表
        Map<String, String> row = new LinkedHashMap<>(headMap.size());
        for (Map.Entry<Integer, String> entry : headMap.entrySet()) {
            row.put(entry.getValue(), data.get(entry.getKey()));
        }
        rowDataList.add(new RowDataDto(context.readRowHolder().getRowIndex(), row));

        // 3. 处理批次数据
        if (rowDataList.size() >= batchSize) {
            rowDataConsumer.accept(rowDataList);
            rowDataList.clear();
        }

        // 4. trace log
        if (trace && ++readCount % batchSize == 0) {
            log.info("map async read excel count: {}", readCount);
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 读取excel完成后填入一个结束标记位，表示读取excel完成。
        rowDataList.add(new RowDataDto(Integer.MIN_VALUE, Collections.emptyMap()));
        rowDataConsumer.accept(rowDataList);
        rowDataList.clear();
    }

    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception {
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException e = (ExcelDataConvertException) exception;
            Field field = e.getExcelContentProperty().getField();
            String msg = String.format("第%d行第%d列格式不正确，Excel列名=%s，读取内容=%s，填充字段=%s, 填充类型=%s",
                    e.getRowIndex() + 1,
                    e.getColumnIndex() + 1,
                    getExcelName(field),
                    e.getCellData().getStringValue(),
                    field.getName(),
                    field.getType().getName());
            throw new CustomException(msg);
        }
        throw exception;
    }

    private String getExcelName(Field field) {
        ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
        return annotation != null ? Arrays.toString(annotation.value()) : null;
    }

}
